package com.jxmobi.util.file;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URI;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 *  zip 压缩，解压缩文件
 * @author Xiaofei Chen <a href="mailto:xchen@jxmobi.com">Email the author</a>
 * @version 1.0 3/23/2016
 */
public class ZipUtils {
    private static final Logger LOGGER = LoggerFactory.getLogger(ZipUtils.class);

    /**
     *  解压给定的zip压缩包到指定的目录下
     * @param zipFileName 待解压缩的zip压缩包
     * @param destDirName 目标文件夹，如果不存在，将创建
     * @throws IOException
     */
    public static void unzip(String zipFileName, String destDirName) throws IOException {

        final Path destDir = Paths.get(destDirName);

        // 如果解压缩的目标文件夹不存在， 创建
        if (Files.notExists(destDir)) {
            LOGGER.debug(destDir + " doesn't exist, creating ... ");
            Files.createDirectories(destDir);
        }

        try (FileSystem zipFileSystem = createZipFileSystem(zipFileName, false)) {
            final Path root = zipFileSystem.getPath("/");

            // 遍历 zip 文件树，并将文件或文件夹拷贝到目标目录
            Files.walkFileTree(root, new SimpleFileVisitor<Path>(){
                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                    final Path dirToCreate = Paths.get(destDir.toString(), dir.toString());

                    if (Files.notExists(dirToCreate)) {
                        LOGGER.debug(String.format("Creating directory %s", dirToCreate));
                        Files.createDirectories(dirToCreate);
                    }
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    final Path destFile = Paths.get(destDir.toString(), file.toString());

                    LOGGER.debug(String.format("Extracting file from %s to %s", file, destFile));
                    Files.copy(file, destFile, StandardCopyOption.REPLACE_EXISTING);
                    return FileVisitResult.CONTINUE;
                }
            });
        }
    }

    /**
      * 查看给定zip包的内容
      * @param zipFileName 待查看的zip包
      * @throws IOException
      * @throws URISyntaxException
      */
    public static void list(String zipFileName) throws IOException {
        LOGGER.debug(String.format("Listing Archive: %s", zipFileName));

        try (FileSystem zipFileSystem = createZipFileSystem(zipFileName, false)) {

            final Path root = zipFileSystem.getPath("/");

            // walk the file and print out the directory and filenames
            Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    print(file);
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                    print(dir);
                    return FileVisitResult.CONTINUE;
                }

                /**
                 *  prints out details about the specified path such as size and modification time
                 * @param file
                 * @throws IOException
                 */
                private void print(Path file) throws IOException {
                    final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    final String modTime = df.format(new Date(Files.getLastModifiedTime(file).toMillis()));
                    LOGGER.debug(String.format("%d\t%s\t%s", Files.size(file), modTime, file));
                }
            });
        }
    }



    /**
     *  创建或者更新zip包文件
     * @param zipFileName 创建或更新的zip包文件的名称
     * @param fileNames 需要添加或更新到zip包的文件或文件夹
     * @throws IOException
     */
    public static void create(String zipFileName, String... fileNames) throws IOException {

        try (FileSystem zipFileSystem = createZipFileSystem(zipFileName, true)) {
            final Path root = zipFileSystem.getPath("/");

            //iterate over the files we need to add
            for (String filename : fileNames) {
                final Path src = Paths.get(filename);

                //add a file to the zip file system
                if(!Files.isDirectory(src)){
                    final Path dest = zipFileSystem.getPath(root.toString(),
                            src.toString());
                    final Path parent = dest.getParent();
                    if(Files.notExists(parent)){
                        LOGGER.debug(String.format("Creating directory %s", parent));
                        Files.createDirectories(parent);
                    }
                    Files.copy(src, dest, StandardCopyOption.REPLACE_EXISTING);
                }
                else{
                    //for directories, walk the file tree
                    Files.walkFileTree(src, new SimpleFileVisitor<Path>(){
                        @Override
                        public FileVisitResult visitFile(Path file,
                                                         BasicFileAttributes attrs) throws IOException {
                            final Path dest = zipFileSystem.getPath(root.toString(),
                                    file.toString());
                            Files.copy(file, dest, StandardCopyOption.REPLACE_EXISTING);
                            return FileVisitResult.CONTINUE;
                        }

                        @Override
                        public FileVisitResult preVisitDirectory(Path dir,
                                                                 BasicFileAttributes attrs) throws IOException {
                            final Path dirToCreate = zipFileSystem.getPath(root.toString(),
                                    dir.toString());
                            if(Files.notExists(dirToCreate)){
                                LOGGER.debug(String.format("Creating directory %s", dirToCreate));
                                Files.createDirectories(dirToCreate);
                            }
                            return FileVisitResult.CONTINUE;
                        }
                    });
                }
            }
        }
    }

    /**
     *  Returns a zip file system
     * @param zipFileName to construct the file system from
     * @param create true if the zip file should be created
     * @return a zip file system
     * @throws IOException
     */
    public static FileSystem createZipFileSystem(String zipFileName, boolean create) throws IOException {

        // convert the filename to a URI
        final Path path = Paths.get(zipFileName);
        final URI uri = URI.create("jar:file:" + path.toUri().getPath());

        final Map<String, String> env = new HashMap<>();
        if (create) {
            env.put("create", "true");
        }

        return FileSystems.newFileSystem(uri, env);
    }
}
